/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 *
 *    (C) 2003-2008, Open Source Geospatial Foundation (OSGeo)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotools.geotools_swing.event;

import java.awt.Component;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;

import org.geotools.geometry.DirectPosition2D;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.geotools_swing.MapPane;


/**
 * A MouseEvent which contains methods to obtain coordinates in real world CoordinateSystem as well
 * as Screen Coordinates.
 * 
 * @author Michael Bedward (adapted from code by Cameron Shorter)
 * @author Andrea Antonello (www.hydrologis.com)
 * @since 2.6
 *
 *
 *
 * @source $URL$
 */
public final class MapMouseEvent extends MouseEvent {
	
	private static final long serialVersionUID = -2457052116118140764L;
	private final DirectPosition2D worldCoords;
    private final boolean isWheelEvent;
    private final int wheelAmount;

    /**
     * Creates a new event instance.
     * 
     * @param pane the source map pane
     * @param event the source mouse event
     */
    public MapMouseEvent(MapPane pane, MouseEvent event) {
    	super((Component) pane, event.getID(), event.getWhen(), event.getModifiers(), event
                .getX(), event.getY(), event.getClickCount(), event.isPopupTrigger(), event
                .getButton());

        worldCoords = calculateWorldPos(pane, event);
        isWheelEvent = false;
        wheelAmount = 0;
    }

    /**
     * Creates a new event instance for a mouse wheel event.
     * 
     * @param pane the source map pane
     * @param event the source mouse wheel event
     */
    public MapMouseEvent(MapPane pane, MouseWheelEvent event) {       
    	super((Component) pane, event.getID(), event.getWhen(), event.getModifiers(), event
                .getX(), event.getY(), event.getClickCount(), event.isPopupTrigger());

        worldCoords = calculateWorldPos(pane, event);
        isWheelEvent = true;
        wheelAmount = event.getWheelRotation();
    }
    
    /**
     * Gets the source map pane for this event.
     */
    @Override
    public MapPane getSource() {
        return (MapPane) super.getSource();
    }
    
    /**
     * Queries whether this event was generated by a mouse wheel action.
     * 
     * @return {@codetrue} if a mouse event
     */
    public boolean isWheelEvent() {
        return isWheelEvent;
    }

    /**
     * Gets the wheel travel amount for a mouse wheel event. 
     * 
     * @return wheel travel, or 0 if this is not a mouse wheel event
     */
    public int getWheelAmount() {
        return wheelAmount;
    }

    /**
     * Gets the position, in map (world) coordinates of this mouse event
     * 
     * @return a new DirectPosition2D object for the world coordinates
     * 
     * @deprecated Please use {@link #getWorldPos} instead
     */
    public DirectPosition2D getMapPosition() {
        return getWorldPos();
    }
    
    /**
     * Gets the mouse position in world coordinates.
     * 
     * @return world position
     */
    public DirectPosition2D getWorldPos() {
        return new DirectPosition2D(
                worldCoords.getCoordinateReferenceSystem(),
                worldCoords.x, worldCoords.y);
    }
    
    /**
     * Gets an envelope of specified width (in world distance units) which is
     * centred on the mouse position.
     * 
     * @param widthWorld envelope width in world units
     * @return the envelope
     * 
     * @throws IllegalArgumentException if {@code widthWorld} is less than zero
     */
    public ReferencedEnvelope getEnvelopeByWorld(double widthWorld) {
        if (widthWorld < 0) {
            throw new IllegalArgumentException("invalid value for widthWorld: " + widthWorld);
        }
        
        double halfw = widthWorld / 2;
        DirectPosition2D worldPos = getWorldPos();
        return new ReferencedEnvelope(
                worldPos.x - halfw, worldPos.x + halfw,
                worldPos.y - halfw, worldPos.y + halfw,
                worldPos.getCoordinateReferenceSystem());
    }
    
    /**
     * Gets an envelope of specified width (in pixels) which is
     * centred on the mouse position.
     * 
     * @param widthPixels envelope width in pixels
     * @return the envelope
     * 
     * @throws IllegalArgumentException if {@code widthPixels} is less than zero
     */
    public ReferencedEnvelope getEnvelopeByPixels(double widthPixels) {
        if (widthPixels < 0) {
            throw new IllegalArgumentException("invalid value for widthPixels: " + widthPixels);
        }
        
        Rectangle2D screenRect = new Rectangle2D.Double(
                getX() - (widthPixels / 2),
                getY() - (widthPixels / 2),
                widthPixels, widthPixels);
        
        MapPane pane = getSource();
        Rectangle2D worldRect = pane.getScreenToWorldTransform().createTransformedShape(
                screenRect).getBounds2D();
        
        return new ReferencedEnvelope(worldRect, 
                pane.getMapContent().getCoordinateReferenceSystem());
    }
    
    /**
     * Helper for constructors to calculate mouse position in world coordinates.
     * 
     * @param pane source map pane
     * @param event source mouse event
     * @return position in world coordinates
     */
    private DirectPosition2D calculateWorldPos(MapPane pane, MouseEvent event) {
        AffineTransform tr = pane.getScreenToWorldTransform();
        DirectPosition2D pos = new DirectPosition2D(event.getX(), event.getY());
        tr.transform(pos, pos);
        pos.setCoordinateReferenceSystem(pane.getMapContent().getCoordinateReferenceSystem());
        return pos;
    }
}
